abidibo.net

Responsive menu for bootstrap

bootstrap css3 javascript jquery

This is a nice one.

Bootstrap is a nice framework, the great advantage of using it is that it saves you a lot of time while building a responsive web site (the same can be said of other css frameworks out there).
The drawback is that the web sites coming out in the last years very often looks the same, all of them have the same navbar and the same menu behavior which collapses for extra-small devices, showing a menu button which reveals the content when pressed.

A good way to differentiate your web site from the others could be a change in the menu collapsing behavior.

Datmenu is a nice responsive menu plugin for Wordpress, so the idea is to replicate its behavior and integrating it in a bootstrapped layout (navbar and menu). Like in the provided gif.

This is quite an easy job, if you know how css transitions work.

The leading actor here is the css property perspective, which gives an element a 3D-space by affecting the distance between the Z plane and the user. I recommend reading more about it in this post on css-tricks.

Then all we need is a bit of javascript and some css rules. I'll use the bootstrap less mixins for brevity. The great part of the javascript code is used to wrap all body contents inside two divs, you may cut such parts while wrapping directly your contents in the mentioned divs in your base template. I've done this in order to get the things working also when you simply can't touch the base template.

The idea is to wrap all the contents inside 2 divs, the external will get the perspective property, the inner one will be rotated and translated.

If you can't wait anymore, and wanna get the code, then surf here or go the end of the post; you need a js and a less file. There are two js files because you can choose your preferred library, mootools or jquery.

Now we'll analyze the js code to understand how to realize such behavior.

Javascript

  1. We need to bind a callback when clicking the menu button
    jQuery(document).ready(function() {
        jQuery('button[data-toggle=collapse]').each(function(index, btn) {
            jQuery(btn).on('click', abidibo.menu.miniaturize);
        });
    })
    
  2. In the callback we have to:
    stop the event propagation: we need this because we'll add a click event to the wrapper container allowing to return to the original layout
    get the target element: it is the content which was collapsed
    calculate the viewport coordinates: these are necessary to fix the width and height of the container which will wrap all the site contents, to create a sort of screenshot. Even if we may use jQuery, it's better to use the window innerHeight and innerWidth properties, since the jQuery window.height() method doesn't work with ff mobile (on my phone).
    // stop propagation
    event.stopPropagation();
    // target
    var target = jQuery(jQuery(this).attr('data-target'));
    // viewport coordinates
    var viewport_height = jQuery(window).height();
    // it seems that translateX 100% takes into account the width of images inside
    // the document even if they have .img-responsive class, so we fix the width
    var viewport_width = jQuery(window).width();
    
  3. Wrap all contents inside two divs: #wrapper the outer and #main-container the inner. We also fix the height and width of #main-container using the previously calculated viewport dimensions
    // wrap all body content inside two divs
    var wrapper = jQuery('<div/>', {id : 'wrapper'});
    // main container height is fixed to viewport height
    var main_container = jQuery('<div/>', {id : 'main-container'}).css({
        height: viewport_height + 'px',
        width: viewport_width + 'px',
        overflow: 'hidden'
    });
    jQuery(document.body).children().wrapAll(main_container);
    jQuery(document.body).children().wrapAll(wrapper);
    
  4. The animation is obtained through css3, as we'll see later, so we only need to add a different class to the body. Such operation is delayed because otherwise can happen that the animation fails, and the final state is reached immediately, probably because the animation affects the #main-container div which was created just now. Then we show the collapsed content (the menu) by cloning it and injecting it into the body
    // add miniature class to body, the delay is necessary to get css3 animation
    setTimeout(function() { jQuery(document.body).addClass('miniature'); }, 100);
    // now show the collapsed content
    var content = target.clone().removeClass('collapse').appendTo(jQuery(document.body));
    
  5. Then we must define a way to go back. We add a click event to the #main-container div, so that when the user clicks it, all returns as it was before showing the menu. We have to:
    stop the event propagation and prevent the default behavior: in fact we have to avoid the normal behavior if the user eventually clicks a link
    create a reverse animation: this is done by setting an intermediate body class 'miniature-back'
    remove the click event
    remove the collapsed and cloned content
    return to the original state: such operation is delayed in order to let the animation end. We have to unwrap all contents and remove the body class. That's it
    // drive me back!
    jQuery(document.body).on('click', '#main-container', function(evt) {
        evt.stopPropagation();
        evt.preventDefault();
        // update selector var
        var main_container = jQuery('#main-container');
        // intermediate class to allow for css transitions
        jQuery(document.body).removeClass('miniature').addClass('miniature-back');
        main_container.off('click');
        content.remove();
        setTimeout(function() {
            // remove wrappers
            main_container.unwrap();
            main_container.children().unwrap();
            jQuery(document.body).removeClass('miniature-back'); 
        }, 1000);
    });
    

Css

Now the css (less using bootstrap mixins):

#main-container {
    .transition-transform(0.4s ease 0s);
}
.miniature {
    background-color: #222;
    #wrapper {
        .perspective(1500px);
        position: fixed;
    }
    #main-container {
        background-color: #fff;
        cursor: pointer;
        .transform-origin(50% 50% 0);
        .transition-transform(0.4s ease 0s);
        -webkit-transform: translateZ(-1500px) translateX(100%) rotateY(-45deg);
        transform: translateZ(-1500px) translateX(100%) rotateY(-45deg);
        .backface-visibility(hidden);
    }

    .navbar-collapse {
        position: absolute;
        top: 20px;
        left: 20px;
        box-shadow: 0 0 0 #222;
    }
}
.miniature-back {
    background-color: #222;
    #wrapper {
        .perspective(1500px);
    }
    #main-container {
        background-color: #fff;
        cursor: pointer;
        .transform-origin(50% 50% 0);
        .transition-transform(0.4s ease 0s);
        -webkit-transform: translateZ(0px) translateX(0%) rotateY(0deg);
        transform: translateZ(0px) translateX(0%) rotateY(0deg);
    }
}

The perspective property is assigned to the #wrapper div, then the inner container #main-container is translated in X and Z and rotated in the Y direction, so we get the "snapshot effect". You can change the animation interval by editing the 0.4s value of the trasition-transform mixin.

Since such collapsing behavior is normally fired with small devices, you can forget about older IE, and the css 3d transforms are well supported by mobile browsers.

You may want to add/change some styles for the .navbar-collapse class, which describes how your menu looks like when the site is miniaturized on the right side.

And now all the code together, powered by gist

You can test it by resizing this window (not anymore).

Hope you enjoy it! Hasta la proxima!

Subscribe to abidibo.net!

If you want to stay up to date with new contents published on this blog, then just enter your email address, and you will receive blog updates! You can set you preferences and decide to receive emails only when articles are posted regarding a precise topic.

I promise, you'll never receive spam or advertising of any kind from this subscription, just content updates.

Subscribe to this blog

Comments are welcome!

blog comments powered by Disqus

Your Smartwatch Loves Tasker!

Your Smartwatch Loves Tasker!

Now available for purchase!

Featured